package com.clei.utils;

import com.clei.utils.log.ConsoleLogger;
import com.clei.utils.log.EmptyLogger;
import com.clei.utils.log.FileLogger;
import com.clei.utils.log.Logger;
import com.clei.utils.log.MixedLogger;
import com.sun.management.OperatingSystemMXBean;

import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

/**
 * 方便自己的输出打印
 *
 * @author KIyA
 */
public final class PrintUtil {

    private static final char TAB = '\t';
    private static final char LINEFEED = '\n';
    private static final String PLACE_STR = "{}";
    private static final int PLACE_LEN = PLACE_STR.length();

    /**
     * 日志打印类class
     */
    private static final String PRINT_CLASS = PrintUtil.class.getCanonicalName();

    /**
     * 默认分隔符
     */
    private static final char SEPARATOR = '-';

    /**
     * 默认分隔线长度
     */
    private static final int SEPARATOR_LINE_LENGTH = 128;

    /**
     * 默认分割线
     */
    private static final String SEPARATOR_LINE;

    /**
     * 排除打印的包名
     */
    private static final Set<String> EXCLUDE_PACKAGES;

    /**
     * logger
     */
    private static Logger logger;

    static {
        // separatorLine
        StringBuilder sb = new StringBuilder(SEPARATOR_LINE_LENGTH);
        for (int i = 0; i < SEPARATOR_LINE_LENGTH; i++) {
            sb.append(SEPARATOR);
        }
        SEPARATOR_LINE = sb.toString();

        // excludePackages
        EXCLUDE_PACKAGES = new HashSet<>(2);
        EXCLUDE_PACKAGES.add("java.lang");
        EXCLUDE_PACKAGES.add("java.util");
        EXCLUDE_PACKAGES.add("io.netty");

        // log
        // 默认console 初始化期间报错也能使用
        logger = new ConsoleLogger();
        URL configUrl = PrintUtil.class.getResource("/log/log.properties");
        if (null == configUrl) {
            PrintUtil.log("读取负载配置文件失败，文件不存在！");
        } else {
            try (InputStream inputStream = configUrl.openStream()) {
                Properties properties = new Properties();
                properties.load(inputStream);

                String output = properties.getProperty("output");
                switch (output) {
                    case "empty":
                        logger = new EmptyLogger();
                        break;
                    case "file":
                        logger = new FileLogger(properties.getProperty("file-path-type"), properties.getProperty("log-file"));
                        break;
                    case "mixed":
                        logger = new MixedLogger(properties.getProperty("file-path-type"), properties.getProperty("log-file"));
                        break;
                    case "console":
                    default:
                }
            } catch (IOException e) {
                PrintUtil.log("读取负载配置文件出错", e);
            }
        }
    }

    private PrintUtil() {
    }

    public static void println() {
        logger.log();
    }

    public static void println(Object obj) {
        logger.log(String.valueOf(obj));
    }

    public static void println(final String str, Object... args) {
        logger.log(formatStr(str, args));
    }

    public static void print(final String str, Object... args) {
        logger.logWithoutLine(formatStr(str, args));
    }

    public static void print(Object obj) {
        logger.logWithoutLine(String.valueOf(obj));
    }

    public static void log() {
        logLine(true);
    }

    public static void log(final String str, Object... args) {
        logLine(true, str, args);
    }

    public static void log(Object obj) {
        logLine(true, String.valueOf(obj));
    }

    public static void log(final String str) {
        logLine(true, str);
    }

    /**
     * 记录日志
     *
     * @param ms 是否打印毫秒数
     */
    public static void logLine(boolean ms) {
        logger.log(prefix(ms));
    }

    /**
     * 记录日志
     *
     * @param ms   是否打印毫秒数
     * @param str  String
     * @param args args
     */
    public static void logLine(boolean ms, final String str, Object... args) {
        String prefix = prefix(ms);
        String data = formatStr(str, args);
        logger.log(prefix + data);
    }

    /**
     * 打印当前日期
     */
    public static void date() {
        print(DateUtil.currentDateTime());
    }

    /**
     * 打印当前日期 + 数据
     *
     * @param obj obj
     */
    public static void date(Object obj) {
        print(DateUtil.currentDateTime() + " - " + obj);
    }

    /**
     * 打印当前内存情况
     */
    public static void printMemoryInfo() {
        int oneM = 1024 * 1024;
        Runtime runtime = Runtime.getRuntime();
        log("jvm maxMemory : {}M", runtime.maxMemory() / oneM);
        log("jvm totalMemory : {}M", runtime.totalMemory() / oneM);
        log("jvm freeMemory : {}M", runtime.freeMemory() / oneM);

        OperatingSystemMXBean system = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
        log("system totalMemory : {}M", system.getTotalPhysicalMemorySize() / oneM);
        log("system freeMemory : {}M", system.getFreePhysicalMemorySize() / oneM);
    }

    /**
     * 字符串格式化
     *
     * @param str  String
     * @param args args
     */
    private static String formatStr(String str, Object... args) {
        if (null == args || args.length == 0) {
            return str;
        }

        StringBuilder sb = new StringBuilder(str);
        // 普通参数
        for (Object arg : args) {
            int position = sb.indexOf(PLACE_STR);
            if (position > -1) {
                sb.replace(position, position + PLACE_LEN, String.valueOf(arg));
            } else {
                break;
            }
        }
        // 异常
        Object lastArg = args[args.length - 1];
        if (lastArg instanceof Throwable) {
            sb.append(getStackTrace((Throwable) lastArg));
        }
        return sb.toString();
    }

    /**
     * 打印分割线
     */
    public static void separatorLine() {
        separatorLine(false);
    }

    /**
     * 打印分割线
     *
     * @param line 是否先换行
     */
    public static void separatorLine(boolean line) {
        if (line) {
            println();
        }
        log(SEPARATOR_LINE);
    }

    /**
     * 打印分割线 使用指定分隔符
     *
     * @param separator 分隔符
     * @param line      是否先换行
     */
    public static void separatorLine(char separator, boolean line) {
        StringBuilder sb = new StringBuilder(SEPARATOR_LINE_LENGTH);
        if (line) {
            sb.append('\n');
        }
        for (int i = 0; i < SEPARATOR_LINE_LENGTH; i++) {
            sb.append(separator);
        }
        log(sb.toString());
    }

    /**
     * 打印当前日期 + 数据 + 换行
     *
     * @param str  String
     * @param args args
     */
    public static void date(final String str, Object... args) {
        print(DateUtil.currentDateTime() + " - " + str, args);
    }

    /**
     * Throwable stackTrace 字符串
     *
     * @param t Throwable
     * @return StackTraceString
     */
    private static String getStackTrace(Throwable t) {
        StringBuilder sb = new StringBuilder();

        sb.append(LINEFEED).append(t.toString()).append(LINEFEED);
        for (StackTraceElement s : t.getStackTrace()) {
            sb.append(TAB + "at ").append(s.toString()).append(LINEFEED);
        }
        for (Throwable se : t.getSuppressed()) {
            sb.append(se.toString()).append(LINEFEED);
        }
        Throwable cause = t.getCause();
        if (null != cause) {
            sb.append(cause).append(LINEFEED);
        }
        return sb.toString();
    }

    /**
     * 获得当前线程名
     *
     * @param ms 是否打印毫秒数
     * @return 打印日志prefix
     */
    private static String prefix(boolean ms) {
        String datetime = DateUtil.currentDateTime(ms);
        Thread currentThread = Thread.currentThread();
        String threadName = currentThread.getName();
        // 非当前日志类，非jdk类，的第一个类
        StackTraceElement[] stackTrace = currentThread.getStackTrace();
        String classMethod = null;
        for (StackTraceElement element : stackTrace) {
            String className = element.getClassName();
            String basePackage = basePackage(className);
            if (!PRINT_CLASS.equals(element.getClassName()) && !EXCLUDE_PACKAGES.contains(basePackage)) {
                classMethod = className + '#' + element.getMethodName() + '#' + element.getLineNumber();
                break;
            }
        }
        if (null == classMethod) {
            StackTraceElement element = stackTrace[stackTrace.length - 1];
            classMethod = element.getClassName() + '#' + element.getMethodName() + '#' + element.getLineNumber();
        }
        return datetime + " [" + threadName + "][" + classMethod + "] ";
    }

    /**
     * 获取基础包名
     * 取前两个单词
     *
     * @param className 全类名
     * @return 基础包名
     */
    private static String basePackage(String className) {
        int dotIndex = StringUtil.indexOf(className, ".", 2);
        if (-1 == dotIndex) {
            dotIndex = className.indexOf('.');
        }
        return className.substring(0, dotIndex);
    }
}
